/* The file is saved in UTF-8 codepage.
 * Check: «Stereotype», Section mark-§, Copyright-©, Alpha-α, Beta-β, Smile-☺
 */
package cz.pecinovsky.english.lootp._134_;


import cz.pecinovsky.english.lootp.manager.Controller;


import cz.pecinovsky.english.lootp.manager.CanvasManager_Envelope.               CanvasManager;


//import cz.pecinovsky.english.lootp.utility.IO;


//import cz.pecinovsky.english.lootp._134_.Auto_134_.Auto;
import cz.pecinovsky.english.lootp._134_.Ring_134_.Ring;
import cz.pecinovsky.english.lootp._134_.RingFactory_140_.RingFactory;
import cz.pecinovsky.english.lootp._134_.IRingFactory_140_.IRingFactory;
import cz.pecinovsky.english.lootp._134_.IRace_138_.IRace;
import cz.pecinovsky.english.lootp._134_.IRacer_137_.IRacer;
import cz.pecinovsky.english.lootp._134_.RoadField_134_.RoadField;
import cz.pecinovsky.english.lootp._134_.Arrow_134_.Arrow;
import cz.pecinovsky.english.lootp._134_.Vozidlo_B_137_.Vozidlo_B;


//import cz.pecinovsky.english.lootp._134_.Závod_T_141_      .Závod_T;
////////////////////////////////////////////////////////////////////////////////
//%%+  +++++ End of ignored starting test - place for imports +++++
//%I+
//import cz.pecinovsky.english.lootp.canvasmanager.IControlled;
//import cz.pecinovsky.english.lootp.canvasmanager.Controller;
//import cz.pecinovsky.english.lootp.canvasmanager.CanvasManager;
//%I-

import cz.pecinovsky.english.lootp.utility.NamedColor;
import cz.pecinovsky.english.lootp.utility.IO;
import cz.pecinovsky.english.lootp.utility.Position;
import cz.pecinovsky.english.lootp.utility.Size;
import cz.pecinovsky.english.lootp.utility.Direction8;

//%A+ >142
import java.util.ArrayList;
//%A-
import java.util.Collection;
//%A+ >142
import java.util.Collections;
import java.util.Comparator;
//%A-
import java.util.HashMap;
//%A+ >142
import java.util.List;
//%A-
import java.util.Map;
//%X+ xxxxx Start of the ovejumped text with declaration of the envelope xxxxx
/*******************************************************************************
 *<pre>
 * Předchozí:   Vychází ze třídy Závod_K
 * Následující: Prozatím není
 *
 * Projekt  140
 *   + Přidáno
 *   - Odebráno
 *   ~ připravDalšíOkruh - u prvního vytvořeného okruhu se zeptá na jeho šířku
 *</pre>
 */
public class Závod_T_141_
{    private Závod_T_141_() {}   static
////////////////////////////////////////////////////////////////////////////////
//%X- ----- End of the ovejumped text with declaration of the envelope -----


/*******************************************************************************
 * Instance třídy {@code Závod_T} představují závody, které je možno absolvovat,
 * Přičemž okruh, na němž se závod pojede, je vytvořen továrnou dodanou
 * konstruktoru daného závodu jako parametr.
 * Závod je charakterizován okruhem, na kterém se pojede, a počtem přihlášených
 * závodníků. Všichni přihlášení závodníci pak pojedou daný okruh současně
 * každý na své instanci příslušného okruhu.
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.03.2017 — 2010-11-08
 */
public class Závod_T implements IRace
{
//== CONSTANT CLASS ATTRIBUTES =================================================

    /** Canvas, na které se bude instance kreslit. */
    private static final CanvasManager CM = CanvasManager.getInstance();

    /** Implicitní maximální povolená width plátna, do nějž se musí vejít
     *  vedle sebe okruhy všech přihlášených závodníků
     *  oddělené navzájem prázdným sloupcem. */
    private static final int IMPLICITNÍ_MAX_ŠÍŘKA_PLÁTNA = 1000;

    /** Implicitní maximální povolená height plátna,
     *  do níž se musí vejít budovaný okruh. */
    private static final int IMPLICITNÍ_MAX_VÝŠKA_PLÁTNA = 700;




//== VARIABLE CLASS ATTRIBUTES =================================================

    /** Maximální povolená bodová width plátna, do nějž se musí vejít vedle
     *  sebe okruhy všech přihlášených závodníků oddělené navzájem prázdným
     *  sloupcem. Z povolené šířky a výšky plátna bude podle počtu přihlášených
     *  závodníků a rozměru okruhu odvozována optimální velikost modulu. */
    private static int maxŠířkaPlátna = IMPLICITNÍ_MAX_ŠÍŘKA_PLÁTNA;

    /** Maximální povolená bodová height plátna, do níž se musí vejít budovaný
     *  okruh. Z povolené šířky a výšky plátna bude podle počtu přihlášených
     *  závodníků a rozměru okruhu odvozována optimální velikost modulu. */
    private static int maxVýškaPlátna = IMPLICITNÍ_MAX_VÝŠKA_PLÁTNA;



//== STATIC CONSTRUCTOR (CLASS INITIALIZER, STATIC INICITALIZING BLOCK) ========
//== CONSTANT INSTANCE ATTRIBUTES ==============================================

    /** Mapa přiřazující jednotlivým závodníkům informace,
     * které si o nich instance závodu pamatuje. */
    private final Map<IRacer, Info> závodníci =
                                       new HashMap<IRacer, Info>();
//%A+ >142

    /** Seznam, do nějž jsou závodníci ukládáni v getIndexOf,
     * v němž dojeli do cíle. */
    private final List<Info> dojezdy = new ArrayList<Info>();
//%A-

    /** Počet kol, na která se závod jede. */
    private final int početKol;

    /** Instance vytvářející okruhy, na nichž se závodí. */
    private final IRingFactory továrna;



//== VARIABLE INSTANCE ATTRIBUTES ==============================================

    /** Aktuálně nastavený module plátna. */
    private int module;

    /** Systémový čas v okamžiku startu závodu. */
    private long čas0;

    /** Počet doposud registrovaných závodníků a tím i vytvořených okruhů. */
    private int závodníků = 0;

    /** Počet sloupců a řádků, které daný okruh zaujímá -
     *  ovlivňují pozici jednotlivých vytvářených okruhů a velikost plátna. */
    private int sloupcůOkruhu, řádkůOkruhu;



//== CLASS GETTERS AND SETTERS =================================================
//== OTHER NON-PRIVATE CLASS METHODS ===========================================

    /***************************************************************************
     * Zapamatuje si maximální povolený rozměr plátna a po přihlášení
     * všech závodníků nastaví module a rozměry plátna tak, aby se co
     * nejefektivněji využila přidělená plocha.
     *
     * @param width Šířka plátna v bodech
     * @param height Výška plátna v bodech
     */
    public static void setMaxRozměrPlátna(int šířka, int výška)
    {
        Závod_T.maxŠířkaPlátna = šířka;
        Závod_T.maxVýškaPlátna = výška;
    }



//##############################################################################
//== CONSTUCTORS AND FACTORY METHODS ===========================================

    /***************************************************************************
     * Vytvoří instanci schopnou pořádat závody na jedno kolo na okruzích
     * vyráběných zadanou továrnou.
     *
     * @param továrna      Továrna, vytvářející okruhy, na nichž se pojede
     */
    public Závod_T(IRingFactory továrna)
    {
        this(továrna, 1);
    }


    /***************************************************************************
     * Vytvoří instanci schopnou pořádat závody na zadaný počet kol
     * na okruzích vyráběných zadanou továrnou.
     *
     * @param továrna      Továrna, vytvářející okruhy, na nichž se pojede
     * @param početKol     Počet kol, na která se závod jede
     */
    public Závod_T(IRingFactory továrna, int početKol)
    {
        this.továrna  = továrna;
        this.početKol = početKol;
        this.module    = CM.getStep();   //Dočasná hodnota platící jen do startu
    }



//== ABSTRACT METHODS ==========================================================
//== INSTANCE GETTERS AND SETTERS ==============================================
//== OTHER NON-PRIVATE INSTANCE METHODS ========================================

    /***************************************************************************
     * Otevře dialogové okno, v němž závodníkům oznámí, že je odstartováno,
     * a poté, co toto okno zavřou, změří systémový čas.
     */
    public void start()
    {
//%A+ >142
        dojezdy.clear();    //V tomto závodě zatím nikdo nedojel
//%A-
        Collection<Info> infa = závodníci.values();
        připravPlátno();  //Upraví jeho velikost tak, aby se okruhy právě vešly
        for (Info info : infa) {
            info.řadič.start();
        }
        IO.inform("Po potvrzení tohoto okna se začne měřit čas");
        čas0 = System.currentTimeMillis();
    }


    /***************************************************************************
     * Ukončí závod, čím zruší všechny registration a připraví se na nové.
     */
    public void stop()
    {
        závodníci.clear();
    }


    /***************************************************************************
     * Zaregistruje zadaného závodníka, umístí jej na start závodu,
     * zjistí si od uživatele potřebné klávesy
     * a přihlásí závodníka jako posluchače klávesnice.
     *
     * @param závodník Závodník registrující se pro daný závod
     */
    @Override
    public void registration(IRacer závodník)
    {
        if (závodníci.containsKey(závodník)) {
            IO.inform("Závodníka nelze zaregistrovat dvakrát: " + závodník);
            return;
        }
        Ring     okruh  = připravDalšíOkruh(); //Opraví i atribut závodníků
        RoadField start  = okruh.getPočátek();
        RoadField cíl    = start.getDalší();
//%I+
//        Controller     řadič  = Controller.vytvořPro(závodník);
//%I-
//%X+
        Controller     řadič  = new Controller(závodník, Controller.getImplicitníKlávesy(),
//                                     new int[] {37, 39, 38, 40, 32, 32, 32},
                                     false);
//%X-

//%I+ <142
//        Info info = new Info(okruh, start, cíl, řadič, početKol, závodník);
//%I-
//%A+ >142
        Info info = new Info(okruh, start, cíl, řadič, početKol,
                             závodník, závodníků);
//%A-
        závodníci.put(závodník, info);
    }


    /***************************************************************************
     * Prověří, že závodník dojel do správné průběžné cílové pozice.
     * Pokud dojel, připraví další průběžnou pozici, pokud nedojel, čeká dál.
     *
     * @param závodník Závodník hlásící změnu své pozice
     */
    @Override
    public void checkpoint(IRacer závodník)
    {
        Info info = závodníci.get(závodník);
        if (info == null) {
            return;                      //==========>
        }
        Position pz = závodník.getPosition();
        Position pc = info.cíl.getPosition();
        if (pz.equals(pc)) {
            if (info.cíl == info.start) {
                info.zbýváKol--;
                if (info.zbýváKol == 0) {
                    konecZávodu(závodník, info);
                    return;                  //==========>
                }
            }
            info.cíl = info.cíl.getDalší();
        }
    }


    /***************************************************************************
     * Vrátí řetězec sloužící jako textový "podpis" instance
     * používaný především při ladění.
     */
    @Override
    public String toString()
    {
        return "Závod_T(továrna=" + továrna + ", počet=" + závodníků +
               ", module=" + module + ")";
    }



//== PRIVATE AND AUXILIARY CLASS METHODS =======================================
//== PRIVATE AND AUXILIARY INSTANCE METHODS ====================================

    /***************************************************************************
     * Připraví další okruh a vrátí odkaz na něj.
     *
     * @return Odkaz na připravený okruh
     */
    private Ring připravDalšíOkruh()
    {
        int x = závodníků * (sloupcůOkruhu + 1) * module;
        Position pozice = new Position(x, 0);
        Ring okruh = továrna.createRing(pozice, NamedColor.getNamedColor(závodníků));
        závodníků = závodníků + 1;
        if (závodníků == 1) {
            Size rozměrOkruhu = okruh.getPRozměr();
            sloupcůOkruhu = rozměrOkruhu.width;
            řádkůOkruhu   = rozměrOkruhu.height;
        }
        return okruh;
    }


    /***************************************************************************
     * Ukončí závod pro zadaného závodníka.
     *
     * @param závodník Závodník ukončivší závod
     */
    private void konecZávodu(IRacer závodník, Info info)
    {
        long nyní = System.currentTimeMillis();
        info.řadič.konec();
        int čas = (int)(nyní - čas0 + 50) / 100;
        System.out.println("Závodník " + závodník.getName() +
               " absolvoval okruh v čase " + čas/10 + "," + čas%10 + " sekund");
//%A+ >142
        //Zapamatujeme si info závodníka s časem jeho dojezdu
        info.čas = čas;
        dojezdy.add(info);
        info.pořadí = dojezdy.size();
//%A-
        závodníci.remove(závodník);
        if (závodníci.isEmpty()) {
//%I+ <142
//            System.out.println("Konec závodu");
//%I-
//%I+ <0
//            IO.inform("Konec závodu" +
//                  "\n\nvýsledky najdete ve standardním výstupu");
//%I-
//%A+ >142
            vyhodnoťZávod();
//%A-
        }
    }


    /***************************************************************************
     * Připraví plátno tak, aby se na něj právě vešly okruhy
     * pro všechny přihlášené závodníky
     */
    private void připravPlátno()
    {
        int  canvasColumns  = (sloupcůOkruhu + 1) * závodníků  -  1;
        int  xKrok = maxŠířkaPlátna / canvasColumns;
        int  yKrok = maxVýškaPlátna / řádkůOkruhu;
        this.module = Math.min(xKrok, yKrok);
        CM.setStepAndSize(module, canvasColumns, řádkůOkruhu);

        CM.dontDraw(); {
            Collection<Info> infa = závodníci.values();
            for (Info info : infa) {
                Ring okruh = info.okruh;
                okruh.setModule(module);
                CM.add(okruh);
                info.závodník.setDirection  (okruh.getPočátek().getSměr());
                info.závodník.setPosition(okruh.getPočátek().getPosition());
                info.závodník.setModule(module);
            }
        } CM.returnDraw();
    }

//%A+ >142

    /***************************************************************************
     * Vyhodnotí celý závod, tj. vypíše záznamy jednotlivých závodníků seřazené
     * nejprve podle umístění, pak podle abecedy a nakonec podle okruhů,
     * na nichž závodníci jeli.
     */
    private void vyhodnoťZávod()
    {
        vypišDojezdy("Výsledné pořadí:");

        Collections.sort(dojezdy);
        vypišDojezdy("Výsledky podle abecedy:");

        Collections.sort(dojezdy, new CompDleOkruhů());
        vypišDojezdy("Výsledky podle okruhů:");
    }


    /***************************************************************************
     * Vypíše na standardní výstup zadanou zprávu následovanou informacemi o
     * jednotlivých závodnících v getIndexOf určeném seznamem {@link #dojezdy}.
     * Každý závodník je vypsán na samostatném řádku a je u něj uvedeno
     * getIndexOf, v němž dojel, okruh, na němž jel, jeho čas a jméno.
     *
     * @param zpráva Zpráva, která celou listinu uvádí
     */
    private void vypišDojezdy(String zpráva)
    {
        System.out.println("\n" + zpráva);
        for (Info info : dojezdy) {
            int    pořadí= info.pořadí;
            String jméno = info.závodník.getName();
            int    čas   = info.čas;
            int    číslo = info.číslo;
            System.out.println(pořadí + ".  o"  + číslo  + " - " +
                               čas/10 + ","     + čas%10 + " - " +  jméno);
        }
    }

//%A-


//== EMBEDDED TYPES AND INNER CLASSES ==========================================

    /***************************************************************************
     * Interní přepravka obsahující základní informace o závodníkovi
     * a průběžném stavu jeho závodu.
     */
//%I+ <142
//    private static class Info
//%I-
//%A+ >142
    private static class Info implements Comparable<Info>
//%A-
    {
        /** Ring, na němž je závoděno. */
        private final Ring okruh;

        /** Kontrolovaný závodník. */
        private final IRacer závodník;

        /** Controller, zprostředkovávající ovládání závodníka z klávesnice. */
        private final Controller řadič;

        /** Pole, z nějž závod odstartuje. */
        private final RoadField start;

        /** Příští pole, na které má závodník dojet
         *  a na němž se bude kontrolovat, zda je skutečně projel.  */
        private RoadField cíl;

        /** Počet kol zbývajících do cíle. */
        private int zbýváKol;
//%A+ >142

        /** Pořadové číslo závodníka a tím i jeho okruhu. */
        int číslo;

        /** Výsledné getIndexOf závodníka. */
        int pořadí;

        /** Počet kol zbývajících do cíle. */
        private int čas;
//%A-


        /***********************************************************************
         * Definuje novou přepravku a inicializuje její atributy.
         */
        Info(Ring okruh, RoadField start, RoadField cíl, Controller řadič, int kol,
//%I+ <142
//             IRacer závodník)
//%I-
//%A+ >142
             IRacer závodník, int číslo)
//%A-
        {
            this.okruh    = okruh;
            this.start    = start;
            this.cíl      = cíl;
            this.řadič    = řadič;
            this.zbýváKol = kol;
            this.závodník = závodník;
//%A+ >142
            this.číslo    = číslo;
//%A-
        }
//%A+ >142


        /***********************************************************************
         * Porovná jméno závodníka se jménem závodníka, jehož info obdrží jako
         * parametr a vrátí číslo označující, které z nich je "větší".
         *
         * @param o Objekt, s nímž je daný objekt porovnáván
         * @return Záporné číslo, je-li daný objekt menší, nula je-li shodný
         *         a kladné číslo je-li větší než parametr.
         */
        @Override
        public int compareTo(Info o)
        {
//            return this.závodník.getName().compareTo(o.závodník.getName());
            String mojeJméno = this.závodník.getName();
            String jehoJméno = o   .závodník.getName();
            return mojeJméno.compareTo(jehoJméno);
        }
//%A-
    }

//%A+ >142

    /***************************************************************************
     * Třída implementující rozhraní {@code Comparator<Info>},
     * kterou potřebujeme k tomu, abychom mohli porovnat dvě infa
     * podle námi zvoleného kritéria - v tomto případě podle okruhu,
     * na němž jel příslušný závodník.
     */
    private static class CompDleOkruhů implements Comparator<Info>
    {
        /***********************************************************************
         * Porovná zadání infa a za větší prohlásí to, jehož okruh
         * má vyšší getIndexOf (je zobraze víc vpravo).
         *
         * @param i1 První porovnávané info
         * @param i2 Druhé porovnávané info
         * @return Záporné číslo, je-li getIndexOf okruhu i1 menší než i2,
         *         nula jsou-li getIndexOf obou okruhů shodná a kladné číslo
         *         je-li getIndexOf okruhu i1 větší než getIndexOf okruhu i2
         */
        @Override
        public int compare(Info i1, Info i2)
        {
            return i1.číslo - i2.číslo;
        }
    }

//%A-


//== TESTING CLASSES AND METHODS ===============================================

    /***************************************************************************
     * Test method.
     */
    public static void test()
    {
        int počet  = 4;
        int rozměr = počet * 100;
        int kol    = 2;
        Závod_T.setMaxRozměrPlátna(rozměr, rozměr);
        IRingFactory ito = new RingFactory(2, 2, Direction8.SOUTH);
        Závod_T závod = new Závod_T(ito, kol);

        for (int i=1;   i <= počet;   i++) {
            Arrow  arrow = new Arrow(0, 0, NamedColor.WHITE);
            Vozidlo_B vb = new Vozidlo_B(arrow);
            vb.setName(NamedColor.getNamedColor(i-1).getName());//"Číslo_" + i);
            vb.registerFor(závod);
        }
        závod.start();
    }
//    /** @param args Parametry příkazového řádku - nepoužívané. */
//    public static void main(String[] args)  {  test();  }
    }

//%%- ---- Start of the ovejumped text with closing of the envelope ----
////////////////////////////////////////////////////////////////////////////////
//== TESTING CLASSES AND METHODS ===============================================

    /***************************************************************************
     * Test method.
     */
    public static void test()
    {
        Závod_T.test();

//        IO.inform("Až se pokocháš, stiskni OK");
//        System.exit(0);
    }
    /** @param args Parametry příkazového řádku - nepoužívané. */
    public static void main(String[] args)  {  test();  }
}
